package io.gitee.zhangbinhub.admin.gateway.log

import io.gitee.zhangbinhub.acp.boot.http.HttpHeaders
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import io.gitee.zhangbinhub.acp.cloud.tools.CloudTools
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.admin.common.constant.CommonConstant
import io.gitee.zhangbinhub.admin.gateway.bus.publish.RouteLogEventPublish
import io.gitee.zhangbinhub.admin.gateway.constant.GateWayConstant
import io.vertx.core.http.HttpClientResponse
import org.noear.snack4.ONode
import org.noear.solon.annotation.Component
import org.noear.solon.cloud.gateway.exchange.ExContext
import org.noear.solon.cloud.gateway.exchange.impl.ExBodyOfBuffer
import org.noear.solon.cloud.gateway.exchange.impl.ExBodyOfStream
import org.noear.solon.core.LoadBalance

/**
 * @since JDK 11
 */
@Component
class RouteLogService(private val logAdapter: LogAdapter, private val cloudTools: CloudTools) {
    private fun getTokenString(bodyStr: String) = ONode.ofJson(bodyStr).let { jsonObj ->
        if (jsonObj.hasKey(GateWayConstant.GATEWAY_HEADER_RESPONSE_TOKEN_FIELD)) {
            jsonObj.get(GateWayConstant.GATEWAY_HEADER_RESPONSE_TOKEN_FIELD).string
        } else {
            null
        }
    }

    fun resolveResponseToken(
        routeLogMessage: RouteLogMessage,
        exContext: ExContext,
        routeLogEventPublish: RouteLogEventPublish
    ) {
        try {
            val exBody = exContext.newResponse().body
            if (exBody is ExBodyOfStream) {
                //这是原始的 body 转发
                (exBody.stream as HttpClientResponse).body().andThen { asyncResult ->
                    if (asyncResult.succeeded()) {
                        val bodyStr = asyncResult.result().toString()
                        getTokenString(bodyStr)?.apply {
                            routeLogMessage.token = this
                            routeLogEventPublish.doRouteLog(routeLogMessage)
                        }
                    }
                }
            } else if (exBody is ExBodyOfBuffer) {
                //body 是可以被修改的（如果你未修改，这块可以去掉）
                val bodyStr = exBody.buffer.toString()
                getTokenString(bodyStr)?.apply {
                    routeLogMessage.token = this
                    routeLogEventPublish.doRouteLog(routeLogMessage)
                }
            }
        } catch (e: Exception) {
            logAdapter.error(e.message, e)
        }
    }

    /**
     * 构建路由日志消息
     */
    fun createRouteLogMessage(exContext: ExContext): RouteLogMessage {
        var token: String? = null
        exContext.rawHeader(HttpHeaders.AUTHORIZATION)?.apply {
            val authList = this.split(" ")
            if (authList.size > 1 && (authList[0].equals("Bearer", ignoreCase = true) || authList[0].equals(
                    "mac",
                    ignoreCase = true
                ))
            ) {
                token = authList[1]
            }
        }
        if (token == null) {
            exContext.rawQueryParam(CommonConstant.ACCESS_TOKEN)?.let {
                if (!CommonTools.isNullStr(it)) {
                    token = it
                }
            }
        }
        return RouteLogMessage(
            logId = exContext.newRequest().headers.get(GateWayConstant.GATEWAY_HEADER_REQUEST_ID),
            remoteIp = exContext.realIp(),
            gatewayIp = cloudTools.getServerIp() + ":" + cloudTools.getServerPort(),
            method = exContext.rawMethod(),
            token = token
        ).also { routeLogMessage ->
            // 设置: applyToken requestTime path serverId targetIp targetUri targetPath
            routeLogMessage.requestTime = System.currentTimeMillis()
            exContext.newRequest().headers.get(GateWayConstant.GATEWAY_HEADER_REQUEST_TIME)?.apply {
                routeLogMessage.requestTime = this.toLong()
            }
            if (routeLogMessage.path == null) {
                routeLogMessage.path = exContext.rawURI().path
            }
            val target = exContext.target()
            if (routeLogMessage.serverId == null && target.scheme.equals(LoadBalance.URI_SCHEME, true)) {
                routeLogMessage.serverId = target.host
            }
        }
    }

}
